/*
 * Copyright 2009 Original Author(s)
 * 
 * This file is part of Kommando
 * 
 * Kommando is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Kommando is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Kommando.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kommando.core.selector;

import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JPopupMenu;

import org.kommando.core.Displayable;
import org.kommando.core.KommandoWindow;
import org.kommando.core.KommandoWindow.Selector;
import org.kommando.core.search.Result;
import org.kommando.core.util.Assert;

/**
 * A selector holds the current search query, the results, the current selection.
 * <p>
 * Also controls the lifecycle of the selection list
 * <p>
 * 
 * 
 * @author Peter De Bruycker
 */
public abstract class AbstractSelector<T extends Displayable> {

    // TODO encapsulate the key handling that impact the query in a separate class

    private List<SelectorListener<T>> listeners = new ArrayList<SelectorListener<T>>();

    private CommandSelector commandSelector;

    private SelectionList<T> selectionList;
    private SelectorState<T> state = new SelectorState<T>();

    private Result<T> currentSelection;

    private Selector selector;

    private boolean active;

    private Timer selectionListTimer = new Timer("ShowSelectionListTimer", true);
    private ShowSelectionListTask showSelectionListTask;

    public AbstractSelector(CommandSelector commandSelector, KommandoWindow.Selector selector) {
        this.commandSelector = commandSelector;
        this.selector = selector;
        selectionList = new SelectionList<T>(this);
    }

    public Selector getSelector() {
        return selector;
    }

    public Result<T> getSelection() {
        return state.getSelection();
    }

    public T getInternalSelection() {
        Result<T> selection = getSelection();
        return selection == null ? null : selection.getValue();
    }

    public void setSelection(Result<T> selection) {
        state.setSelection(selection);

        setState(state);
    }

    // TODO rename method
    protected void queryChanged(String query) {
        List<Result<T>> searchResults = search(query);

        state.setQuery(query);
        state.setSelection(searchResults.isEmpty() ? null : searchResults.get(0));
        state.setResults(searchResults);

        setState(state);

        scheduleShowSelectionListTask();
    }

    protected abstract List<Result<T>> doSearch(List<T> candidates, String query);

    protected List<Result<T>> search(String query) {
        return doSearch(state.getCandidates(), query);
    }

    public void prepareForDisplay() {
        setActive(false);
        setState(getInitialState());
    }

    private void showSelectionList() {
        cancelShowSelectionListTask();
        selectionList.show(getWindow().getSelectionListPopupPosition(selector));
    }

    protected abstract JPopupMenu createPopupMenu(T item);

    protected KommandoWindow getWindow() {
        return getCommandSelector().getWindow();
    }

    private void hideSelectionList() {
        cancelShowSelectionListTask();
        selectionList.hide();
    }

    public boolean isSelectionListVisible() {
        return selectionList.isVisible();
    }

    public void addSelectorListener(SelectorListener<T> listener) {
        listeners.add(listener);
    }

    public void removeSelectorListener(SelectorListener<T> listener) {
        listeners.remove(listener);
    }

    public SelectorState<T> getState() {
        return state;
    }

    public void setState(SelectorState<T> state) {
        Assert.argumentNotNull("state", state);

        Result<T> oldSelection = currentSelection;

        this.state = state;
        this.currentSelection = state.getSelection();

        if (!isSameSelection(oldSelection, state.getSelection())) {
            for (SelectorListener<T> listener : listeners) {
                listener.selectionChanged(state.getSelection());
            }
        }

        if (getWindow() != null) {
            getWindow().setSelection(selector, state.getSelection());
        }

        selectionList.setState(state);
    }

    private boolean isSameSelection(Result<T> oldSelection, Result<T> newSelection) {
        if (oldSelection == null && newSelection == null) {
            return true;
        }

        if (oldSelection == null || newSelection == null) {
            return false;
        }

        return oldSelection.equals(newSelection);
    }

    public boolean handleKeyEvent(CommandSelector commandSelector, KeyEvent e) {
        // TODO start timer (250ms) which shows the list popup, cancel the timer
        // that's already running

        if (e.getID() == KeyEvent.KEY_RELEASED) {
            if (this.isSelectionListVisible()) {
                if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
                    this.hideSelectionList();
                    return true;
                }
            } else {
                if (e.getKeyCode() == KeyEvent.VK_DOWN) {
                    this.showSelectionList();
                    return true;
                }
            }
        }

        String query = state.getQuery();

        if (e.getID() == KeyEvent.KEY_RELEASED) {
            // TODO define valid key ranges
            if (e.getKeyChar() >= 'a' && e.getKeyChar() <= 'z' || e.getKeyChar() >= '0' && e.getKeyChar() <= '9') {
                queryChanged(query + e.getKeyChar());
                return true;
            }

            if (e.getKeyCode() == KeyEvent.VK_DELETE) {
                queryChanged("");
                return true;
            }

            if (e.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
                if (query.length() > 0) {
                    queryChanged(query.substring(0, query.length() - 1));
                }
                return true;
            }
        }

        return false;
    }

    public CommandSelector getCommandSelector() {
        return commandSelector;
    }

    protected abstract SelectorState<T> getInitialState();

    public void init() {
        setState(getInitialState());
    }

    public boolean isActive() {
        return active;
    }

    public void setActive(boolean active) {
        this.active = active;

        if (!active) {
            hideSelectionList();
        }
    }

    private void cancelShowSelectionListTask() {
        // if (showSelectionListTask != null) {
        // showSelectionListTask.cancel();
        // showSelectionListTask = null;
        // }
    }

    private void scheduleShowSelectionListTask() {
        // cancelShowSelectionListTask();
        //
        // showSelectionListTask = new ShowSelectionListTask();
        // selectionListTimer.schedule(showSelectionListTask, 750);
    }

    private class ShowSelectionListTask extends TimerTask {
        @Override
        public void run() {
            showSelectionList();
        }
    }
}
